+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + Systemprogrammierung in PCQ-Pascal + + Kurs für AMIGAGadget und Purity- Teil II + +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Bisherige Kursteile : Teil I : Voraussetzungen,Screens,Windows Und hallo ! Und hier wären wir zum zweiten Teil dieses tollen Kurses ! Alle da ? Jaaaa !! Gut. Also, ich muß noch eine Entschuldigung anbringen : ähm...stotter...die CloseWork-Routine war ein leichter Schuß in den Ofen.... Man sollte erst das Window und dann den Screen schließen. Funktioniert hat es aber trotzdem. Beim letzten Mal haben wir Screens und Windows behandelt. Wir können jetzt also unsern eigenen Bildschirm aufmachen. So weit, so gut. Doch was machen wir jetzt mit dem ganzen ? Das Zeug liegt leer rum und stört nur. Die Dinger müssen doch zu was zu gebrauchen sein. Ein bißchen drin rummalen und Text ausgeben wäre gar nicht uninteressant. Deshalb III. Einfache Grafikausgabe Bisher sind wir ja ganz gut mit der "Intuition.Library" ausgekommen. Das reicht nun aber nicht mehr. Wir brauchen etwas, was uns hilft, unsere optischen Vorstellungen zu verwirklichen - die "Graphics.Library". Was die Intuition-Library für Screens und Windows ist, ist die Graphics-Library für Rastports und Bitmaps. Was,was,was ?? Das war zugegebenermaßen gemein von mir - einen neuen Begriff mit zwei anderen neuen zu erklären. Ein Rastport ist ein Graphics-Element, das den vereinfachten Zugriff auf Bitmaps ermöglicht. Bitmap nennt man die Verknüpfung mehrerer Bitplanes (siehe Teil 1 !!). Die Verbindung zwischen Graphics und Intuition ist z.B. das Window, das wiederum einen vereinfachten Zugriff auf Rastports ermöglicht. Genug der grauen Theorie. Wie malen wir jetzt einen Punkt in unser Window ? Ganz einfach : WritePixel (window^.RPort,xk,yk) Man benötigt einen Zeiger auf das Window und übergibt der WritePixel -Routine die x- und die y-Koordinate des zu zeichnenden Punktes. Fertig. Wer jetzt suchend um sich schaut und nicht versteht, was x- und y-Koordinaten sind und noch nie so etwas gegessen hat, dem sei geholfen. Man kann sich ein Window als Koordinatenkreuz (mal wieder in der Schule gepennt ??) vorstellen. Durch so ein Koordinatenkreuz lässt sich jeder Punkt einer Fläche als Zahlenpaar beschrieben. Die x-Koordinate beschreibt die Entfernung vom linken Rand, die y-Koordinate normalerweise vom unteren Rand. Gemeinerweise ist das beim Computer nicht so. Der Ursprung (0/0) liegt hier nicht links unten, sondern links oben. Aha ! Also zeigt die y-Koordinate den Abstand vom oberen Rand. Logisch, oder ? Wenn wir ein Window der Breite 400 aufgemacht haben, dann gehen unsere x-Koordinaten von 0 bis 400. Aha ! Feine Sache, oder ? Eins sollte man aber beachten : Wenn man x-Werte von 0 oder 1, bzw. von 399 oder 400 verwendet, dann würde in so einem Window (400er Breite) der Rahmen beschädigt. Also : aufpassen mit dem Rand (auch gerade die Titelleiste !!). Das wär's dann wohl. Also legen wir doch gleich mal los .... HAAAAAAALT !!! Reingefallen ! Wir brauchen noch etwas ganz Wichtiges ! Wir wollen ja die Graphics-LIBRARY benutzen ! Und was müssen wir machen, damit wir sie auch verwenden können ? Richtig - sie muß erstmal aufgemacht werden. Da bietet uns das Include-File "Exec/Libraries.I" eine brauchbare Routine : OpenLibrary. Sie benötigt den Namen der Library und die Versionsnummer, die die Library mindestens haben muß. Als Rückgabewert erhalten wir einen Pointer auf eine Library. Was wir nun mit dem machen ? Ganz einfach. PCQ erwartet ihn an einer ganz bestimmten Stelle. Nämlich in der Variablen "GfxBase", die im File "Graphics/Graphics.I" deklariert wird. Als dann, nun können wir wohl beginnen... Weit gefehlt ! WritePixel bringt einfach einen Punkt auf den Bildschirm. Wir wollen aber einen in der Vordergrundfarbe und nicht etwa einen in der Hintergrundfarbe !! Also müssen wir die Zeichenfarbe festlegen : SetAPen (window^.RPort , farbregister) Farbregister 0 wäre die Hintergrundfarbe. Wir nehmen also Farbregister 1. Aber jetzt geht es los ! Wieder falsch. Schaut man nämlich mal in das File "Graphics.I", so sieht man, daß die Befehle hier nicht sind. Sie müssen also woanders sein. Gesucht und gefunden im File "Pens.i". Und jetzt, ohne lange drumherum zu reden : Program KursProgramm (input , output); { Listing für den AMIGAGadget/Purity - Pascalkurs , } { Erste Schritte in der Grafikprogrammierung } {$I "Include:Intuition/Intuition.I" } {$I "Include:Exec/Libraries.I" } {$I "Include:Graphics/Graphics.I" } {$I "Include:Libraries/Dos.I" } {$I "Include:Graphics/Pens.I" } VAR myscreen : ScreenPtr; mywindow : WindowPtr; PROCEDURE CloseDisplay; { Sollte ein Teil des Displays offen sein, dann wird er von } { dieser Routine geschlossen. } BEGIN IF mywindow<>NIL THEN CloseWindow (mywindow); IF myscreen<>NIL THEN CloseScreen (myscreen); IF GfxBase<>NIL THEN CloseLibrary (GfxBase); END; PROCEDURE BreakProgram (reason : STRING); { Diese Routine schließt alles bisher geöffnete, druckt den } { Fehlergrund aus und bricht dann das Programm ab. } BEGIN CloseDisplay; WRITELN ('Program error : ',reason); Exit (42); END; PROCEDURE OpenLibs; CONST GfxName : String = "graphics.library"; BEGIN GfxBase:=OpenLibrary (GfxName,0); END; PROCEDURE OpenDisplay; { Diese Routine erstellt ein Display. } CONST mynewscreen : NewScreen = (0,0,640,256,2,0,1,HIRES, CUSTOMSCREEN_f,NIL, "UnserGrafikscreen",NIL,NIL); mynewwindow : NewWindow = (0,0,400,200,0,1,0, SMART_REFRESH+ACTIVATE+ WINDOWDRAG,NIL,NIL,"Unser Grafikwindow", NIL,NIL,0,0,0,0,CUSTOMSCREEN_f); BEGIN myscreen := OpenScreen (Adr(mynewscreen)); IF myscreen=NIL THEN BreakProgram ("Couldn't open Screen"); mynewwindow.Screen:=myscreen; mywindow := OpenWindow (Adr(mynewwindow)); IF mywindow=NIL THEN BreakProgram ("Couldn't open Window"); END; PROCEDURE WarteEinWenig; { Also, das mit den FOR-Schleifen ist mir jetzt doch zu } { unelegant ! Wir binden daher also "libraries/dos.i" ein } { und benutzen nun den Befehl Delay (taktzyklen). } BEGIN Delay (500); END; PROCEDURE ZeichneEtwas; VAR ze1 : INTEGER; BEGIN SetAPen (mywindow^.RPort,1); FOR ze1:=1 TO 200 DO WritePixel (mywindow^.RPort,ze1*2,ze1); { zeichnet eine löchrige schräge Linie } END; BEGIN OpenLibs; OpenDisplay; ZeichneEtwas; WarteEinWenig; CloseDisplay; END. Sieht ja schon ganz schnucklig aus. Oder ? Doch es wäre etwas mühsam, wenn man nur den Befehl WritePixel hätte, um Grafiken zu erstellen.... Also gibt es noch ein paar andere wichtige. Einen kurzen Überblick über die, die man am häufigsten braucht : Move (window^.RPort , x , y) - bewegt den Grafikcursor an eine Position des Rastports, ohne etwas zu zeichnen. WritePixel bewegt den Grafikcursor zwar auch, zeichnet aber einen Punkt Draw (window^.RPort , x , y) - zeichnet eine Linie von der Position des Grafikcursors zu den neuen Koordinaten RectFill (window^.RPort , xmin,ymin,xmax,ymax)- zeichnet ein ausgefülltes Rechteck mit den vier Eckpunkten E1 (xmin / ymin) E2 (xmin / ymax) E3 (xmax / ymax) E4 (xmax / ymin) DrawEllipse (window^.RPort, cx , cy , a , b) - zeichnet eine Ellipse mit dem Mittelpunkt M (cx / cy) und einem maximalen Radius in x-Ausdehnung von a Pixeln und in y-Ausdehnung von b Pixeln DrawCircle (window^.RPort , cx , cy , a ) - Sonderfall von DrawEllipse. X- und y-Radius sind hier gleich. Aber Achtung : Gerade bei der sogenannten Med-Res-Auflösung (640*256) sind das bei weitem keine Kreise mehr !! ReadPixel (window^.RPort,x,y)- liefert als Rückgabewert das Farbregister des Bildschirmpunktes an der Stelle P (x/y) Das sollte für's erste mal reichen. In der nächsten Folge kommen dann noch ein paar dazu. Es gibt noch weitaus mehr, die aber für den Einstieg zu weit führen und zu umfassende Kenntnisse erfordern, als sie hier in den wenigen Zeilen vermittelt werden können. Was allerdings einen großen Platz einnehmen wird sind selbstgeschriebene Prozeduren, die man dann als Grafikbefehle einsetzen kann. So brauchen wir ganz dringend einen, um Linien zu ziehen. Sinnigerweise sollten wir ihn Line nennen. Eine Linie hat einen Anfangs- und einen Endpunkt. Diese müssen übergeben werden. Außerdem natürlich der RastPort. Also, jeder mal ran an einen Stift und schnell so eine Prozedur entworfen....... Fertig ? Gut. Sie sollte etwa so aussehen : PROCEDURE Line (rp : Address ; xs , ys , xe , ye : Short); BEGIN Move (rp , xs , ys); Draw (rp , xe , ye); END; Einfach, oder ? Und nun gibt es noch eine gewisse Form, die man oft auf Computerbildschirmen sieht....ja....ja....genau, das Rechteck. Um ausgefüllte Exemplare der Gattung zu bekommen, haben wir ja den RectFill-Befehl. Wie zeichnen wir nun aber nur den Rahmen ?? Eine neue Prozedur muß her ! PROCEDURE Box (rp : Address ; xs , ys , xe , ye : Short); BEGIN Line (rp,xs,ys,xe,ys); Line (rp,xe,ys,xe,ye); Line (rp,xe,ye,xs,ye); Line (rp,xs,ye,xs,ys); END; In Sachen Taktzyklen ausnützen sicher nicht das Optimum. Dafür anschaulich und kurz. Nun wollen wir mal kräftig alle unsere Kenntnisse ausspielen. Ein Multi -Grafik-Programm ! Vier Fenster, in denen jeweils andere Grafiken gezeichnet werden sollen. Also dann : Vorhang auf ! Program KursProgramm (input , output); { Listing für den AMIGAGadget/Purity - Pascalkurs , } { Zweite Schritte in der Grafikprogrammierung } {$I "Include:Intuition/Intuition.I" } {$I "Include:Exec/Libraries.I" } {$I "Include:Graphics/Graphics.I" } {$I "Include:Libraries/Dos.I" } {$I "Include:Graphics/Pens.I" } VAR myscreen : ScreenPtr; mywindow : ARRAY [1..4] OF WindowPtr; PROCEDURE Line (rp : Address ; xs , ys , xe , ye : Short); BEGIN Move (rp , xs , ys); Draw (rp , xe , ye); END; PROCEDURE Box (rp : Address ; xs , ys , xe , ye : Short); BEGIN Line (rp,xs,ys,xe,ys); Line (rp,xe,ys,xe,ye); Line (rp,xe,ye,xs,ye); Line (rp,xs,ye,xs,ys); END; PROCEDURE CloseDisplay; VAR i : Integer; { Sollte ein Teil des Displays offen sein, dann wird er von } { dieser Routine geschlossen. } BEGIN FOR i:=1 TO 4 DO IF mywindow[i]<>NIL THEN CloseWindow (mywindow[i]); IF myscreen<>NIL THEN CloseScreen (myscreen); IF GfxBase<>NIL THEN CloseLibrary (GfxBase); END; PROCEDURE BreakProgram (reason : STRING); { Diese Routine schließt alles bisher geöffnete, druckt den } { Fehlergrund aus und bricht dann das Programm ab. } BEGIN CloseDisplay; WRITELN ('Program error : ',reason); Exit (42); END; PROCEDURE OpenLibs; CONST GraphicName : String = "graphics.library"; BEGIN GfxBase:=OpenLibrary (GraphicName,0); END; PROCEDURE OpenDisplay; { Diese Routine erstellt ein Display. } CONST mynewscreen : NewScreen = (0,0,640,256,1,0,1,HIRES, CUSTOMSCREEN_f,NIL, "Die MultiGrafik-Show !",NIL,NIL); NewWin1 : NewWindow = (0,0,320,128,0,1,0,SMART_REFRESH+WINDOWDRAG+ ACTIVATE+SMART_REFRESH,NIL,NIL,"Multi-GFX-Show 1", NIL,NIL,0,0,0,0,CUSTOMSCREEN_f); NewWin2 : NewWindow = (320,0,320,128,0,1,0,SMART_REFRESH+WINDOWDRAG+ ACTIVATE+SMART_REFRESH,NIL,NIL,"Multi-GFX-Show 2", NIL,NIL,0,0,0,0,CUSTOMSCREEN_f); NewWin3 : NewWindow = (0,128,320,128,0,1,0,SMART_REFRESH+WINDOWDRAG+ ACTIVATE+SMART_REFRESH,NIL,NIL,"Multi-GFX-Show 3", NIL,NIL,0,0,0,0,CUSTOMSCREEN_f); NewWin4 : NewWindow = (320,128,320,128,0,1,0,SMART_REFRESH+WINDOWDRAG+ ACTIVATE+SMART_REFRESH,NIL,NIL,"Multi-GFX-Show 4", NIL,NIL,0,0,0,0,CUSTOMSCREEN_f); BEGIN myscreen := OpenScreen (Adr(mynewscreen)); IF myscreen=NIL THEN BreakProgram ("Couldn't open Screen"); NewWin1.Screen:=myscreen; NewWin2.Screen:=myscreen; NewWin3.Screen:=myscreen; NewWin4.Screen:=myscreen; mywindow[1] := OpenWindow (Adr(NewWin1)); mywindow[2] := OpenWindow (Adr(NewWin2)); mywindow[3] := OpenWindow (Adr(NewWin3)); mywindow[4] := OpenWindow (Adr(NewWin4)); IF mywindow[4]=NIL THEN BreakProgram ("Couldn't open Window"); END; PROCEDURE WarteEinWenig; BEGIN Delay (1000); END; PROCEDURE MultiShow; VAR ze1 : INTEGER; r : Real; { Wir durchlaufen eine Schleife, in der für jedes Window ein } { Zeichenbefehl aufgerufen wird. } BEGIN FOR ze1:=1 TO 4 DO SetAPen (mywindow[ze1]^.RPort,1); FOR ze1:=0 TO 50 DO BEGIN { Zeichenbefehl für Window 1 } RectFill (mywindow[1]^.RPort,2+(ze1*3),12+(ze1), 2+(ze1*5),24+(ze1*2)); IF (ze1 / 2)<>(ze1 DIV 2) THEN BEGIN SetAPen (mywindow[1]^.RPort,0); RectFill (mywindow[1]^.RPort,3+(ze1*3),13+(ze1), 1+(ze1*5),23+(ze1*2)); SetAPen (mywindow[1]^.RPort,1); END; { Zeichenbefehl für Window 2 } Line (mywindow[2]^.RPort,10+(6*ze1),20,310-(6*ze1),120); Line (mywindow[2]^.RPort,10,20+(ze1*2),310,120-(ze1*2)); { Zeichenbefehl für Window 3 } r:=ze1/50*3.141; Box(mywindow[3]^.RPort,160+TRUNC(Cos(r)*120),60+TRUNC(Sin(r)*50), 180+TRUNC(Cos(r)*120),70+TRUNC(Sin(r)*50)); { Zeichenbefehl für Window 4 } DrawEllipse (mywindow[4]^.RPort,160,64,ze1*3,ze1); END; END; BEGIN OpenLibs; OpenDisplay; MultiShow; WarteEinWenig; CloseDisplay; END. Boah, oder ??? Zu dem Programm muß ich noch etwas sagen : ich habe jetzt 3 (!!!!) Stunden lang alle möglichen Programmteile umgestellt, immer wieder neu compiliert und muß zugeben : ich stehe vor einem Rätsel. Nach dem Compilieren läuft das Programm. Wenn ich mit meiner Pascal-Disk neu boote läuft's meistens nicht.... Jetzt hab ich mit meiner Systemdisk gebootet und es läuft - weiß der Geier, was da nicht richtig ist ! (Schwache Leistung, ich geb's ja zu, aber ich hab eigentlich heute noch was anderes vor....) Das sollte wohl für diesmal wieder genügen. Probiert schön mit den neuen Möglichkeiten und ziert Euch nicht, Fragen, Verbesserungsvorschläge, Lob oder sonstwas an die Purity-Macher oder an mich zu schicken. Andreas Neumann - Auf dem Ruhbühl 151 - W 7997 Immenstaad Bess dann ! © 28.12.1991 by Andreas Neumann für Gadget Amiga von Nils Kassube und Purity von Steppenbrand und Diesel